package top.jpower.jpower.service.core.role.impl;

import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.lang.tree.Tree;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import top.jpower.jpower.dbs.dao.core.client.TbCoreClientDao;
import top.jpower.jpower.dbs.dao.core.role.TbCoreFunctionDao;
import top.jpower.jpower.dbs.dao.core.role.TbCoreFunctionMenuDao;
import top.jpower.jpower.dbs.dao.core.role.TbCoreRoleFunctionDao;
import top.jpower.jpower.dbs.dao.core.role.mapper.TbCoreFunctionMapper;
import top.jpower.jpower.dbs.entity.core.function.TbCoreFunction;
import top.jpower.jpower.dbs.entity.core.function.TbCoreFunctionMenu;
import top.jpower.jpower.dbs.entity.core.role.TbCoreRoleFunction;
import top.jpower.jpower.module.common.auth.RoleConstant;
import top.jpower.jpower.module.common.service.impl.BaseServiceImpl;
import top.jpower.jpower.module.common.utils.Fc;
import top.jpower.jpower.module.common.utils.ShieldUtil;
import top.jpower.jpower.module.common.utils.StringUtil;
import top.jpower.jpower.module.common.utils.constants.ConstantsEnum;
import top.jpower.jpower.module.common.utils.constants.StringPool;
import top.jpower.jpower.module.config.FunctionGenerate;
import top.jpower.jpower.module.mp.support.Condition;
import top.jpower.jpower.module.mp.support.LambdaTreeWrapper;
import top.jpower.jpower.service.core.role.CoreFunctionService;
import top.jpower.jpower.vo.core.DataFunctionVo;
import top.jpower.jpower.vo.core.FunctionVo;

import java.util.*;
import java.util.stream.Collectors;

import static top.jpower.jpower.module.common.utils.constants.JpowerConstants.TOP_CODE;

/**
 * @author mr.gmac
 */
@Service("coreFunctionService")
@AllArgsConstructor
public class CoreFunctionServiceImpl extends BaseServiceImpl<TbCoreFunctionMapper, TbCoreFunction> implements CoreFunctionService {

    private final String ROLE_SQL = "select function_id from tb_core_role_function where role_id in ({})";

    private TbCoreFunctionDao coreFunctionDao;
    private TbCoreRoleFunctionDao coreRoleFunctionDao;
    private TbCoreFunctionMenuDao functionMenuDao;
    private TbCoreClientDao clientDao;

    @Override
    public List<Tree<String>> treeMenuTypeByClientId(List<String> roleIds, String clientId) {
        return coreFunctionDao.treeMenuTypeByClientId(roleIds, clientId);
    }

    @Override
    public List<FunctionVo> listFunction(Map<String,Object> coreFunction) {

        String functionType = null;
        if (coreFunction.containsKey("functionType_eq")){
            functionType = Fc.toStr(coreFunction.get("functionType_eq"));
        }

        String menuId = Fc.toStr(coreFunction.remove("menuId_eq"));

        LambdaQueryWrapper<TbCoreFunction> wrapper =
                Condition.getQueryWrapper(coreFunction,TbCoreFunction.class).lambda()
                        .inSql(!ShieldUtil.isRoot(),TbCoreFunction::getId,StringUtil.format(ROLE_SQL,StringPool.SINGLE_QUOTE.concat(Fc.join(ShieldUtil.getUserRole(),StringPool.SINGLE_QUOTE_CONCAT)).concat(StringPool.SINGLE_QUOTE)))
                        .inSql(Fc.isNotBlank(menuId),TbCoreFunction::getId,StringUtil.format("select function_id from tb_core_function_menu where menu_id = '{}'",menuId))
                        .orderByAsc(TbCoreFunction::getSort);

        return coreFunctionDao.getBaseMapper().listFunction(wrapper, functionType);
    }

    @Override
    public Boolean add(TbCoreFunction coreFunction) {
        if (Fc.isBlank(coreFunction.getParentId()) || Fc.equalsValue(coreFunction.getParentId(), TOP_CODE)){
            coreFunction.setParentId(TOP_CODE);
            coreFunction.setAncestorId(TOP_CODE);
        }else {
            coreFunction.setAncestorId(coreFunction.getParentId().concat(StringPool.COMMA).concat(Fc.toStr(coreFunctionDao.getById(coreFunction.getParentId()).getAncestorId())));
        }
        return coreFunctionDao.save(coreFunction);
    }

    @Override
    public Boolean delete(String ids) {
        coreRoleFunctionDao.removeReal(Condition.<TbCoreRoleFunction>getQueryWrapper().lambda().in(TbCoreRoleFunction::getFunctionId, Fc.toStrList(ids)));
        functionMenuDao.removeReal(Condition.<TbCoreFunctionMenu>getQueryWrapper().lambda().eq(TbCoreFunctionMenu::getFunctionId,Fc.toStrList(ids)));
        return coreFunctionDao.removeRealByIds(Fc.toStrList(ids));
    }

    @Override
    public long listByPids(String ids) {
        return coreFunctionDao.count(Condition.<TbCoreFunction>getQueryWrapper()
                .lambda()
                .in(TbCoreFunction::getParentId,Fc.toStrList(ids))
                .notIn(TbCoreFunction::getId,Fc.toStrList(ids)));
    }

    @Override
    public TbCoreFunction selectFunctionByCode(String code) {
        LambdaQueryWrapper<TbCoreFunction> wrapper = new QueryWrapper<TbCoreFunction>().lambda();
        wrapper.eq(TbCoreFunction::getCode,code);
        return coreFunctionDao.getOne(wrapper);
    }

    @Override
    public TbCoreFunction selectFunctionByUrl(String url) {
        LambdaQueryWrapper<TbCoreFunction> wrapper = new QueryWrapper<TbCoreFunction>().lambda();
        wrapper.eq(TbCoreFunction::getUrl,url);
        return coreFunctionDao.getOne(wrapper,false);
    }

    @Override
    public Boolean update(TbCoreFunction coreFunction) {

        TbCoreFunction function = coreFunctionDao.getById(coreFunction.getId());
        if (Fc.notNull(function) && !Fc.equalsValue(function.getParentId(),coreFunction.getParentId())){
            if (Fc.isNotBlank(coreFunction.getParentId()) && Fc.isBlank(function.getParentId())){
                functionMenuDao.removeReal(Condition.<TbCoreFunctionMenu>getQueryWrapper().lambda().eq(TbCoreFunctionMenu::getFunctionId,function.getId()));
            }
        }

        if (Fc.isNotBlank(coreFunction.getParentId())){
            coreFunction.setAncestorId(StringUtil.replace(function.getAncestorId(),function.getParentId(),coreFunction.getParentId()));
        }

        return coreFunctionDao.updateById(coreFunction);
    }

    @Override
    public boolean hierarchySave(String parentId, List<String> ids) {
        //把和顶级菜单有关联得关系删除
        functionMenuDao.removeReal(Condition.<TbCoreFunctionMenu>getQueryWrapper()
                .lambda().in(TbCoreFunctionMenu::getFunctionId,ids));

        List<TbCoreFunction> list = coreFunctionDao.list(Condition.<TbCoreFunction>getQueryWrapper().lambda().in(TbCoreFunction::getId,ids).or(or->{
            for (String id : ids) {
                or.like(TbCoreFunction::getAncestorId, id).or();
            }
        }));

        TbCoreFunction parentFunction = coreFunctionDao.getById(parentId);

        List<String> oldParents = list.stream().filter(f->ids.contains(f.getId())).map(TbCoreFunction::getParentId).collect(Collectors.toList());

        list.forEach(f->{
            if (ids.contains(f.getId())){
                f.setParentId(parentId);
                f.setAncestorId(parentFunction.getAncestorId().concat(StringPool.COMMA).concat(parentId));
            }else {
                f.setAncestorId(StringUtil.replaceFirst(f.getAncestorId(), oldParents, parentId));
            }
        });

        return coreFunctionDao.updateBatchById(list);
    }

    @Override
    public Set<String> queryUrlIdByRole(String roleIds) {
        return new HashSet<>(coreRoleFunctionDao.listObjs(Condition.<TbCoreRoleFunction>getQueryWrapper()
                .lambda()
                .select(TbCoreRoleFunction::getFunctionId)
                .in(TbCoreRoleFunction::getRoleId,Fc.toStrList(roleIds)),Fc::toStr));
    }

    @Override
    public List<Tree<String>> lazyTreeByRole(String parentId, List<String> roleIds) {
        String inSql = StringPool.SINGLE_QUOTE.concat(Fc.join(roleIds, StringPool.SINGLE_QUOTE_CONCAT)).concat(StringPool.SINGLE_QUOTE);
        return coreFunctionDao.tree(Condition.getLambdaTreeWrapper(TbCoreFunction.class, TbCoreFunction::getId, TbCoreFunction::getParentId)
                .lazy(parentId)
                .select(TbCoreFunction::getFunctionName, TbCoreFunction::getUrl, TbCoreFunction::getSort)
                .inSql(TbCoreFunction::getId, StringUtil.format(ROLE_SQL,inSql)));
    }

    /**
     * 客户端下的接口资源
     *
     * @param roleIds  角色ID
     * @param clientId 客户端ID
     * @return 接口资源
     * @author mr.g
     **/
    @Override
    public List<Map<String, Object>> listInterface(List<String> roleIds, String clientId) {
        return coreFunctionDao.listInterface(roleIds,clientId);
    }

    @Override
    public List<String> getUrlsByRoleIds(List<String> roleIds, String clientCode) {
        String inSql = StringUtils.collectionToDelimitedString(roleIds, StringPool.COMMA, StringPool.SINGLE_QUOTE, StringPool.SINGLE_QUOTE);

        return coreFunctionDao.listObjs(Condition.<TbCoreFunction>getQueryWrapper().lambda()
                .select(TbCoreFunction::getUrl)
                .isNotNull(TbCoreFunction::getUrl)
                .eq(TbCoreFunction::getClientId,clientDao.queryIdByCode(clientCode))
                .inSql(TbCoreFunction::getId, StringUtil.format(ROLE_SQL,inSql)),Fc::toStr);
    }

    @Override
    public List<TbCoreFunction> listMenuByRoleId(List<String> roleIds, String clientCode, String topMenuId,boolean isHide) {

        if (Fc.isEmpty(roleIds)){
            return new ArrayList<>();
        }

        String inSql = StringPool.SINGLE_QUOTE.concat(Fc.join(roleIds,StringPool.SINGLE_QUOTE_CONCAT)).concat(StringPool.SINGLE_QUOTE);
        List<TbCoreFunction> list = coreFunctionDao.list(Condition.<TbCoreFunction>getQueryWrapper().lambda()
                .eq(TbCoreFunction::getFunctionType, ConstantsEnum.FUNCTION_TYPE.MENU.getValue())
                .eq(TbCoreFunction::getClientId,clientDao.queryIdByCode(clientCode))
                .eq(isHide, TbCoreFunction::getIsHide, ConstantsEnum.YN01.N.getValue())
                .inSql(!ShieldUtil.isRoot(), TbCoreFunction::getId,StringUtil.format(ROLE_SQL,inSql))
                .orderByAsc(TbCoreFunction::getSort));

        //获取顶部菜单关联的左侧菜单
        if (Fc.isNotBlank(topMenuId)){
            Set<String> listId = new HashSet<>(functionMenuDao.listObjs(Condition.<TbCoreFunctionMenu>getQueryWrapper().lambda().select(TbCoreFunctionMenu::getFunctionId).eq(TbCoreFunctionMenu::getMenuId,topMenuId), Fc::toStr));
            listId.addAll(findDescendants(list,listId));
            list = list.stream().filter(function -> listId.contains(function.getId())).collect(Collectors.toList());
        }
        return list;
    }

    /**
     * 查找子孙级别
     *
     * @author mr.g
     * @param listAll 全部功能
     * @param listId 一级功能ID
     * @return java.util.List<top.jpower.jpower.dbs.entity.function.TbCoreFunction>
     **/
    private Set<String> findDescendants(List<TbCoreFunction> listAll, Set<String> listId) {

        Set<String> childrenId = listAll.stream().filter(function -> listId.contains(function.getParentId())).map(TbCoreFunction::getId).collect(Collectors.toSet());

        if (Fc.isNotEmpty(childrenId)) {
            listId.addAll(findDescendants(listAll, childrenId));
        }

        return listId;
    }

    @Override
    public List<Tree<String>> menuTreeByRoleIds(List<String> roleIds, String clientId, String topMenuId) {
        LambdaTreeWrapper<TbCoreFunction> wrapper = Condition.getLambdaTreeWrapper(TbCoreFunction.class,TbCoreFunction::getId,TbCoreFunction::getParentId)
                .select(TbCoreFunction::getFunctionName,TbCoreFunction::getCode,TbCoreFunction::getUrl,TbCoreFunction::getSort)
                .eq(TbCoreFunction::getFunctionType, ConstantsEnum.FUNCTION_TYPE.MENU.getValue())
                // 如果不是超级用户，则查出自己权限的菜单
                .inSql(!ShieldUtil.isRoot(),TbCoreFunction::getId,StringUtil.format(ROLE_SQL,StringPool.SINGLE_QUOTE.concat(Fc.join(roleIds,StringPool.SINGLE_QUOTE_CONCAT)).concat(StringPool.SINGLE_QUOTE)));

        List<Tree<String>> list = coreFunctionDao.tree(wrapper.eq(TbCoreFunction::getClientId,clientId).orderByAsc(TbCoreFunction::getSort));

        if (Fc.isBlank(topMenuId)){
            return list;
        }

        List<String> functionIds = functionMenuDao.listObjs(Condition.<TbCoreFunctionMenu>getQueryWrapper().lambda().select(TbCoreFunctionMenu::getFunctionId).eq(TbCoreFunctionMenu::getMenuId,topMenuId),Fc::toStr);
        if (Fc.isEmpty(functionIds)){
            return new ArrayList<>();
        }
        return list.stream().filter(tree->functionIds.contains(tree.getId())).collect(Collectors.toList());
    }

    @Override
    public List<TbCoreFunction> menuByRoleIds(List<String> roleIds) {
        if (Fc.isEmpty(roleIds)){
            return new ArrayList<>();
        }

        return coreFunctionDao.list(Condition.<TbCoreFunction>getQueryWrapper()
                .lambda()
                .eq(TbCoreFunction::getFunctionType, ConstantsEnum.FUNCTION_TYPE.MENU.getValue())
                .func(q->{
                    if (!ShieldUtil.isRoot()){
                        q.inSql(TbCoreFunction::getId,StringUtil.format(ROLE_SQL, StringPool.SINGLE_QUOTE.concat(Fc.join(ShieldUtil.getUserRole(),StringPool.SINGLE_QUOTE_CONCAT)).concat(StringPool.SINGLE_QUOTE)));
                    }
                })
                .orderByAsc(TbCoreFunction::getSort));
    }

    @Override
    public List<String> listBtnByRoleId(List<String> roleIds) {
        String inSql = StringPool.SINGLE_QUOTE.concat(Fc.join(roleIds,StringPool.SINGLE_QUOTE_CONCAT)).concat(StringPool.SINGLE_QUOTE);

        String clientId = clientDao.queryIdByCode(ShieldUtil.getClientCode());

        return coreFunctionDao.listObjs(Condition.<TbCoreFunction>getQueryWrapper().lambda()
                .select(TbCoreFunction::getCode)
                .eq(TbCoreFunction::getFunctionType, ConstantsEnum.FUNCTION_TYPE.BTN.getValue())
                .eq(TbCoreFunction::getClientId,clientId)
                .inSql(!ShieldUtil.isRoot(), TbCoreFunction::getId,StringUtil.format(ROLE_SQL,inSql)),Fc::toStr);
    }

    @Override
    public List<Tree<String>> listTreeByRoleId(List<String> roleIds) {
        String inSql = StringPool.SINGLE_QUOTE.concat(Fc.join(roleIds, StringPool.SINGLE_QUOTE_CONCAT)).concat(StringPool.SINGLE_QUOTE);
        List<Tree<String>> list = coreFunctionDao.tree(Condition.getLambdaTreeWrapper(TbCoreFunction.class, TbCoreFunction::getId, TbCoreFunction::getParentId)
                .eq(TbCoreFunction::getClientId,clientDao.queryIdByCode(ShieldUtil.getClientCode()))
                .inSql(TbCoreFunction::getId, StringUtil.format(ROLE_SQL, inSql)).orderByAsc(TbCoreFunction::getSort));
        return list;
    }

    @Override
    public long queryRoleByUrl(String url) {
        TbCoreFunction function = selectFunctionByUrl(url);
        if (!Fc.isNull(function)){
            long roleCount = coreRoleFunctionDao.count(Condition.<TbCoreRoleFunction>getQueryWrapper().lambda()
                    .eq(TbCoreRoleFunction::getRoleId, RoleConstant.ANONYMOUS_ID)
                    .eq(TbCoreRoleFunction::getFunctionId,function.getId()));
            return roleCount;
        }
        return 0;
    }

    @Override
    public List<Tree<String>> treeButByMenu(List<String> roleIds, String parentId, String clientId) {

        List<String> parentIds = coreFunctionDao.listObjs(Condition.<TbCoreFunction>getQueryWrapper().lambda()
                        .select(TbCoreFunction::getId)
                        .eq(TbCoreFunction::getClientId,clientId)
                        .eq(TbCoreFunction::getFunctionType, ConstantsEnum.FUNCTION_TYPE.BTN.getValue())
                        .eq(TbCoreFunction::getParentId,parentId)
                        .inSql(!ShieldUtil.isRoot(),TbCoreFunction::getId,StringUtil.format(ROLE_SQL,StringPool.SINGLE_QUOTE.concat(Fc.join(roleIds,StringPool.SINGLE_QUOTE_CONCAT)).concat(StringPool.SINGLE_QUOTE)))
                ,Fc::toStr);
        if (Fc.isEmpty(parentIds)){
            return ListUtil.toList();
        }

        return coreFunctionDao.tree(Condition.getLambdaTreeWrapper(TbCoreFunction.class, TbCoreFunction::getId, TbCoreFunction::getParentId)
                .select(TbCoreFunction::getFunctionName,TbCoreFunction::getAlias,TbCoreFunction::getCode,TbCoreFunction::getUrl,TbCoreFunction::getFunctionType)
                .eq(TbCoreFunction::getClientId,clientId)
                .eq(TbCoreFunction::getFunctionType, ConstantsEnum.FUNCTION_TYPE.BTN.getValue())
                .inSql(!ShieldUtil.isRoot(),TbCoreFunction::getId,StringUtil.format(ROLE_SQL,StringPool.SINGLE_QUOTE.concat(Fc.join(roleIds,StringPool.SINGLE_QUOTE_CONCAT)).concat(StringPool.SINGLE_QUOTE)))
                .like(TbCoreFunction::getAncestorId,parentId)
                .func(q->{
                    if (Fc.equalsValue(parentId, TOP_CODE)){
                        q.and(and->{
                            and.eq(TbCoreFunction::getParentId, parentId);
                            for (String pId: parentIds){
                                and.or(or->or.like(TbCoreFunction::getAncestorId, pId));
                            }
                        });
                    } else {
                        q.like(TbCoreFunction::getAncestorId,parentId);
                    }
                })
                .orderByAsc(TbCoreFunction::getSort));
    }

    @Override
    public boolean generateFunction() {

        //去拿到所有的功能点
        Map<String,List<Map<String,String>>> map = FunctionGenerate.functions;
        if (Fc.isNotEmpty(map)){
            //拿到所有的菜单
            List<TbCoreFunction> menus = coreFunctionDao.list(Condition.<TbCoreFunction>getQueryWrapper().lambda().eq(TbCoreFunction::getFunctionType, ConstantsEnum.FUNCTION_TYPE.MENU.getValue()));
            if (Fc.isNotEmpty(menus)){
                //拿到所有的code
                List<String> allCode = coreFunctionDao.listObjs(Condition.<TbCoreFunction>getQueryWrapper().lambda().select(TbCoreFunction::getCode),Fc::toStr);

                //存储每个code的父级BTN的CODE
                Map<String,String> codeMap = new HashMap<>();

                //存储要保存的功能
                List<TbCoreFunction> functionList = new ArrayList<>();
                map.forEach((menuCode,functions)->{
                    Optional<TbCoreFunction> menu = menus.stream().filter(f->Fc.equalsValue(f.getCode(),menuCode)).findAny();
                    menu.ifPresent(tbCoreFunction -> functions.forEach(fun -> {
                        String code = fun.get("code");
                        //只要不重复的code才去存储
                        if (functionList.stream().noneMatch(f -> Fc.equalsValue(f.getCode(), code)) && !allCode.contains(code)) {
                            TbCoreFunction function = new TbCoreFunction();
                            function.setCode(code);
                            function.setFunctionName(fun.get("name"));
                            function.setAlias(fun.get("alias"));
                            function.setUrl(fun.get("url"));
                            function.setClientId(tbCoreFunction.getClientId());
                            if (Fc.isBlank(fun.get("btnCode"))){
                                function.setParentId(tbCoreFunction.getId());
                            }else {
                                codeMap.put(code, fun.get("btnCode"));
                            }
                            function.setFunctionType(Fc.toInt(fun.get("type")));
                            function.setTarget(ConstantsEnum.FUNCTION_TARGET.SELF.getValue());
                            function.setIsHide(Boolean.FALSE);
                            functionList.add(function);
                        }
                    }));
                });


                //去保存功能点
                if (Fc.isNotEmpty(functionList)){
                    coreFunctionDao.addBatchSomeColumn(functionList.stream().filter(f->Fc.isNotBlank(f.getParentId())).collect(Collectors.toList()));

                    List<TbCoreFunction> funcs = functionList.stream().filter(f->Fc.isBlank(f.getParentId())).collect(Collectors.toList());
                    if (Fc.isNotEmpty(funcs)){
                        Map<String,TbCoreFunction> idCode = coreFunctionDao.selectIdByCode(new HashSet<>(codeMap.values()));
                        coreFunctionDao.addBatchSomeColumn(funcs.stream().peek(f-> {
                            TbCoreFunction parent = idCode.get(codeMap.get(f.getCode()));
                            f.setParentId(parent.getId());
                            f.setAncestorId(parent.getAncestorId().concat(StringPool.COMMA).concat(parent.getId()));
                        }).collect(Collectors.toList()));
                    }
                }
            }
        }
        return true;
    }

    /**
     * 查询菜单列表
     *
     * @param coreFunction
     * @return
     * @author mr.g
     **/
    @Override
    public List<DataFunctionVo> listDataFunction(Map<String, Object> coreFunction) {
        String menuId = Fc.toStr(coreFunction.remove("menuId_eq"));

        return coreFunctionDao.getBaseMapper().listDataFunction(Condition.getQueryWrapper(coreFunction,TbCoreFunction.class).lambda()
                .inSql(!ShieldUtil.isRoot(),TbCoreFunction::getId,StringUtil.format(ROLE_SQL,StringPool.SINGLE_QUOTE.concat(Fc.join(ShieldUtil.getUserRole(),StringPool.SINGLE_QUOTE_CONCAT)).concat(StringPool.SINGLE_QUOTE)))
                .inSql(Fc.isNotBlank(menuId),TbCoreFunction::getId,StringUtil.format("select function_id from tb_core_function_menu where menu_id = '{}'",menuId))
                .orderByAsc(TbCoreFunction::getSort));
    }

}
